package com.hsjk.szwj.module.pay.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hsjk.szwj.framework.common.exception.ServiceException;
import com.hsjk.szwj.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.hsjk.szwj.framework.common.util.string.StringKit;
import com.hsjk.szwj.framework.mq.core.RedisMQTemplate;
import com.hsjk.szwj.module.pay.constant.PayOrderConstant;
import com.hsjk.szwj.module.pay.controller.app.order.vo.UnifiedOrderReqVO;
import com.hsjk.szwj.module.pay.controller.app.order.vo.UnifiedOrderRespVO;
import com.hsjk.szwj.module.pay.dal.dataobject.CommonPayResult;
import com.hsjk.szwj.module.pay.dal.dataobject.config.ChannelRetMsg;
import com.hsjk.szwj.module.pay.dal.dataobject.config.MchAppConfigContext;
import com.hsjk.szwj.module.pay.dal.dataobject.config.PayOrderReissueMQ;
import com.hsjk.szwj.module.pay.dal.dataobject.order.PayOrderDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.IsvInfoDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.MchAppDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.MchInfoDO;
import com.hsjk.szwj.module.pay.dal.dataobject.system.MchPayPassageDO;
import com.hsjk.szwj.module.pay.enums.MchTypeEnum;
import com.hsjk.szwj.module.pay.exception.ChannelException;
import com.hsjk.szwj.module.pay.service.ConfigContextQueryService;
import com.hsjk.szwj.module.pay.service.IIsvInfoService;
import com.hsjk.szwj.module.pay.service.IMchAppService;
import com.hsjk.szwj.module.pay.service.IMchInfoService;
import com.hsjk.szwj.module.pay.service.IPaymentService;
import com.hsjk.szwj.module.pay.service.MchPayPassageService;
import com.hsjk.szwj.module.pay.service.PayInterfaceDefineService;
import com.hsjk.szwj.module.pay.service.PayOrderProcessService;
import com.hsjk.szwj.module.pay.service.PayOrderService;
import com.hsjk.szwj.module.pay.util.AmountUtil;
import com.hsjk.szwj.module.train.api.ICheckoutConfigApi;
import com.hsjk.szwj.module.train.api.IOrderApi;
import com.hsjk.szwj.module.train.api.ISkuDivisionApi;
import com.hsjk.szwj.module.train.api.dto.DivisionReceiverDTO;
import com.hsjk.szwj.module.train.api.dto.OrderDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

/**
 * @author chenjiawei
 * @desc
 * @Date 2023/08/22 00:37
 **/
@Slf4j
public class AbstractPayOrderController extends PayApiController {
    @Resource
    private MchPayPassageService mchPayPassageService;
    @Resource
    private PayOrderService payOrderService;
    @Resource
    private ConfigContextQueryService configContextQueryService;
    @Resource
    private PayOrderProcessService payOrderProcessService;
    @Resource
    private PayInterfaceDefineService payInterfaceDefineService;
    @Resource
    private RedisMQTemplate redisMQTemplate;
    @Resource
    protected HttpServletRequest request;
    @Resource
    protected HttpServletResponse response;
    @Resource
    private IMchInfoService mchInfoService;
    @Resource
    private IMchAppService mchAppService;
    @Resource
    private IIsvInfoService iIsvInfoService;
    @Resource
    private IOrderApi orderApi;
    @Resource
    private ICheckoutConfigApi checkoutConfigApi;

    @Resource
    private ISkuDivisionApi skuDivisionApi;

    /**
     * 统一下单 (新建订单模式)
     **/
    protected CommonPayResult unifiedOrder(String wayCode, UnifiedOrderReqVO bizRQ) {
        return unifiedOrder(wayCode, bizRQ, null);
    }

    /**
     * 统一下单
     **/
    protected CommonPayResult unifiedOrder(String wayCode, UnifiedOrderReqVO bizRQ, PayOrderDO payOrder) {

        // 响应数据
        UnifiedOrderRespVO bizRS = null;
        //是否新订单模式 [  一般接口都为新订单模式，  由于QR_CASHIER支付方式，需要先 在DB插入一个新订单， 导致此处需要特殊判断下。 如果已存在则直接更新，否则为插入。  ]
        boolean isNewOrder = payOrder == null;

        try {
            //当订单存在时，封装公共参数。
            if (payOrder != null) {

                if (payOrder.getState() != PayOrderConstant.STATE_INIT) {
                    throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode()
                            , "订单状态异常");
                }
                MchInfoDO mchInfoDO = mchInfoService.getMchInfoById(payOrder.getMchId());
                MchAppDO mchAppDO = mchAppService.getById(payOrder.getAppId());
                // 需要将订单更新 支付方式
                payOrder.setWayCode(wayCode);
                //更新渠道用户信息
                payOrder.setChannelUser(bizRQ.getChannelUserId());
                bizRQ.setMchId(mchInfoDO.getId());
                bizRQ.setAppId(mchAppDO.getId());
                bizRQ.setMchOrderId(payOrder.getMchOrderId());
                bizRQ.setWayCode(wayCode);
                bizRQ.setAmount(payOrder.getAmount());
                bizRQ.setCurrency(payOrder.getCurrency());
                bizRQ.setClientIp(payOrder.getClientIp());
                bizRQ.setSubject(payOrder.getSubject());
                bizRQ.setNotifyUrl(payOrder.getNotifyUrl());
                bizRQ.setReturnUrl(payOrder.getReturnUrl());
                bizRQ.setChannelExtra(payOrder.getChannelExtra());
                bizRQ.setExtParam(payOrder.getExtParam());
                bizRQ.setDivisionMode(payOrder.getDivisionMode());
            }

            Long mchId = bizRQ.getMchId();
            Long appId = bizRQ.getAppId();
            MchInfoDO mchInfoDO = mchInfoService.getMchInfoById(mchId);
            // 只有新订单模式，进行校验
            if (isNewOrder && payOrderService
                    .count(new LambdaQueryWrapper<PayOrderDO>()
                            .eq(PayOrderDO::getMchId, mchInfoDO.getId())
                            .eq(PayOrderDO::getMchOrderId, bizRQ.getMchOrderId())) > 0) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "商户订单[" + bizRQ.getMchOrderId() + "]已存在");
            }

            if (StringUtils.isNotEmpty(bizRQ.getNotifyUrl()) && !StringKit.isAvailableUrl(bizRQ.getNotifyUrl())) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "异步通知地址协议仅支持http:// 或 https:// !");
            }
            if (StringUtils.isNotEmpty(bizRQ.getReturnUrl()) && !StringKit.isAvailableUrl(bizRQ.getReturnUrl())) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "同步通知地址协议仅支持http:// 或 https:// !");
            }

            //获取支付参数 (缓存数据) 和 商户信息
            MchAppConfigContext mchAppConfigContext =
                    configContextQueryService.queryMchInfoAndAppInfo(mchId, appId);
            if (mchAppConfigContext == null) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "获取商户应用信息失败");
            }

            MchInfoDO mchInfo = mchAppConfigContext.getMchInfo();
            MchAppDO mchApp = mchAppConfigContext.getMchApp();


            // 根据支付方式， 查询出 该商户 可用的支付接口
            MchPayPassageDO mchPayPassage =
                    mchPayPassageService.findMchPayPassage(mchInfo.getId(),
                            mchApp.getId(), wayCode);
            if (mchPayPassage == null) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "商户应用不支持该支付方式");
            }
            //获取支付接口
            IPaymentService paymentService =
                    checkMchWayCodeAndGetService(mchAppConfigContext, mchPayPassage);
            String ifCode = paymentService.getIfCode();
            Long ifCodeId = payInterfaceDefineService.getIdByCode(ifCode);
            //生成订单
            if (isNewOrder) {
                payOrder = genPayOrder(bizRQ, mchInfo, mchApp, ifCodeId, mchPayPassage);
            } else {
                payOrder.setIfId(ifCodeId);
                // 查询支付方式的费率，并 在更新ing时更新费率信息
                payOrder.setMchFeeRate(mchPayPassage.getRate());
                //商户手续费,单位分
                payOrder.setMchFeeAmount(AmountUtil.calPercentageFee(payOrder.getAmount(),
                        payOrder.getMchFeeRate()));
            }

            //预先校验
            String errMsg = paymentService.preCheck(bizRQ, payOrder);
            if (StringUtils.isNotEmpty(errMsg)) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), errMsg);
            }

            if (isNewOrder) {
                //订单入库 订单状态： 生成状态  此时没有和任何上游渠道产生交互。
                payOrderService.save(payOrder);
            }

            //调起上游支付接口
            bizRS = (UnifiedOrderRespVO) paymentService.pay(bizRQ, payOrder, mchAppConfigContext);

            //处理上游返回数据
            this.processChannelMsg(bizRS.getChannelRetMsg(), payOrder);

            return packageApiResByPayOrder(bizRQ, bizRS, payOrder);

        } catch (ServiceException e) {
            return CommonPayResult.error(e);

        } catch (ChannelException e) {

            //处理上游返回数据
            this.processChannelMsg(e.getChannelRetMsg(), payOrder);

            if (e.getChannelRetMsg().getChannelState() == ChannelRetMsg.ChannelState.SYS_ERROR) {
                return CommonPayResult.error(e.getMessage());
            }

            return this.packageApiResByPayOrder(bizRQ, bizRS, payOrder);


        } catch (Exception e) {
            log.error("系统异常：{}", e);
            return CommonPayResult.error("系统异常");
        }
    }

    private PayOrderDO genPayOrder(UnifiedOrderReqVO rq, MchInfoDO mchInfo,
                                   MchAppDO mchApp, Long ifId,
                                   MchPayPassageDO mchPayPassage) {
        boolean ifDivision = false;
        OrderDTO orderInfo = orderApi.getOrderInfoByOrderNo(String.valueOf(rq.getMchOrderId()));
        if (ObjectUtil.isNotNull(orderInfo)) {
            List<DivisionReceiverDTO> skuDivisionList = skuDivisionApi.getSkuDivisionListBySkuId(orderInfo.getSkuId());
            if (CollUtil.isNotEmpty(skuDivisionList)) {
                ifDivision = true;
            }
        }
        PayOrderDO payOrder = new PayOrderDO();
        //生成订单ID
        payOrder.setId(rq.getPayOrderId());
        //商户号
        payOrder.setMchId(mchInfo.getId());
        //服务商号
        payOrder.setIsvId(mchInfo.getIsvId());
        //商户名称（简称）
        payOrder.setMchName(mchInfo.getMchShortName());
        //商户类型
        payOrder.setMchType(mchInfo.getMchType());
        //商户订单号
        payOrder.setMchOrderId(rq.getMchOrderId());
        //商户应用appId
        payOrder.setAppId(mchApp.getId());
        //接口代码
        payOrder.setIfId(ifId);
        //支付方式
        payOrder.setWayCode(rq.getWayCode());
        //订单金额
        payOrder.setAmount(rq.getAmount());

        if (mchPayPassage != null) {
            //商户手续费费率快照
            payOrder.setMchFeeRate(mchPayPassage.getRate());
        } else {
            //预下单模式， 按照0计算入库， 后续进行更新
            payOrder.setMchFeeRate(BigDecimal.ZERO);
        }
        //商户手续费,单位分
        payOrder.setMchFeeAmount(AmountUtil.calPercentageFee(payOrder.getAmount(), payOrder.getMchFeeRate()));
        //币种
        payOrder.setCurrency(rq.getCurrency());
        //订单状态, 默认订单生成状态
        payOrder.setState(PayOrderConstant.STATE_INIT);
        //客户端IP
        payOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(),
                getClientIp()));
        //商品标题
        payOrder.setSubject(rq.getSubject());
        //商品描述信息
        payOrder.setBody(rq.getBody());
//        payOrder.setChannelExtra(rq.getChannelExtra()); //特殊渠道发起的附件额外参数,  是否应该删除该字段了？？ 比如authCode不应该记录， 只是在传输阶段存在的吧？  之前的为了在payOrder对象需要传参。
        //渠道用户标志
        payOrder.setChannelUser(rq.getChannelUserId());
        //商户扩展参数
        payOrder.setExtParam(rq.getExtParam());
        //异步通知地址
        payOrder.setNotifyUrl(rq.getNotifyUrl());
        //页面跳转地址
        payOrder.setReturnUrl(rq.getReturnUrl());

        // 分账模式
        if (ifDivision) {
            payOrder.setDivisionMode(ObjectUtils.defaultIfNull(rq.getDivisionMode(),
                    PayOrderConstant.DIVISION_MODE_AUTO));
        }
        //订单过期时间 单位： 秒
        if (rq.getExpiredTime() != null) {
            payOrder.setExpiredTime(rq.getExpiredTime());
        } else {
            //订单过期时间 默认两个小时
            payOrder.setExpiredTime(LocalDateTime.now().plusHours(2));
        }
        //订单创建时间
        payOrder.setCreateTime(LocalDateTime.now());
        return payOrder;
    }


    /**
     * 校验： 商户的支付方式是否可用
     * 返回： 支付接口
     **/
    private IPaymentService checkMchWayCodeAndGetService(MchAppConfigContext mchAppConfigContext,
                                                         MchPayPassageDO mchPayPassage) {

        // 接口代码
        Long ifId = mchPayPassage.getIfId();
        String ifCode = payInterfaceDefineService.getCodeById(ifId);
        IPaymentService paymentService = SpringUtil.getBean(ifCode + "PaymentService",
                IPaymentService.class);
        if (paymentService == null) {
            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                    "无此支付通道接口");
        }

        if (!paymentService.isSupport(mchPayPassage.getWayCode())) {
            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                    "接口不支持该支付方式");
        }
        //普通商户
        if (MchTypeEnum.NORMAL.getValue().equals(mchAppConfigContext.getMchType())) {

            if (configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchId(),
                    mchAppConfigContext.getAppId(), ifId) == null) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "商户应用参数未配置");
            }
        } else if (MchTypeEnum.ISVSUB.getValue().equals(mchAppConfigContext.getMchType())) {
            //特约商户
            if (configContextQueryService.queryIsvsubMchParams(mchAppConfigContext.getMchId()
                    , mchAppConfigContext.getAppId(), ifId) == null) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "特约商户参数未配置");
            }
            IsvInfoDO isvInfoDO =
                    iIsvInfoService.getIsvInfoById(mchAppConfigContext.getMchInfo().getIsvId());
            if (configContextQueryService
                    .queryIsvParams(isvInfoDO.getId(),
                            ifId) == null) {
                throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                        "服务商参数未配置");
            }
        }

        return paymentService;

    }


    /**
     * 处理返回的渠道信息，并更新订单状态
     * payOrder将对部分信息进行 赋值操作。
     **/
    private void processChannelMsg(ChannelRetMsg channelRetMsg, PayOrderDO payOrder) {

        //对象为空 || 上游返回状态为空， 则无需操作
        if (channelRetMsg == null || channelRetMsg.getChannelState() == null) {
            return;
        }

        Long payOrderId = payOrder.getId();

        //明确成功
        if (ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == channelRetMsg.getChannelState()) {

            this.updateInitOrderStateThrowException(PayOrderConstant.STATE_SUCCESS,
                    payOrder, channelRetMsg);

            //订单支付成功，其他业务逻辑
            payOrderProcessService.confirmSuccess(payOrder);

            //明确失败
        } else if (ChannelRetMsg.ChannelState.CONFIRM_FAIL == channelRetMsg.getChannelState()) {

            this.updateInitOrderStateThrowException(PayOrderConstant.STATE_FAIL, payOrder, channelRetMsg);

            // 上游处理中 || 未知 || 上游接口返回异常  订单为支付中状态
        } else if (ChannelRetMsg.ChannelState.WAITING == channelRetMsg.getChannelState() ||
                ChannelRetMsg.ChannelState.UNKNOWN == channelRetMsg.getChannelState() ||
                ChannelRetMsg.ChannelState.API_RET_ERROR == channelRetMsg.getChannelState()

        ) {
            this.updateInitOrderStateThrowException(PayOrderConstant.STATE_ING, payOrder, channelRetMsg);

            // 系统异常：  订单不再处理。  为： 生成状态
        } else if (ChannelRetMsg.ChannelState.SYS_ERROR == channelRetMsg.getChannelState()) {

        } else {

            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                    "ChannelState 返回异常！");
        }

        //判断是否需要轮询查单
        if (channelRetMsg.isNeedQuery()) {
            redisMQTemplate.send(PayOrderReissueMQ.build(payOrderId, 1));
        }

    }


    /**
     * 更新订单状态 --》 订单生成--》 其他状态  (向外抛出异常)
     **/
    private void updateInitOrderStateThrowException(Integer orderState, PayOrderDO payOrder,
                                                    ChannelRetMsg channelRetMsg) {

        payOrder.setState(orderState);
        payOrder.setChannelOrderNo(channelRetMsg.getChannelOrderId());
        payOrder.setErrCode(channelRetMsg.getChannelErrCode());
        payOrder.setErrMsg(channelRetMsg.getChannelErrMsg());

        // 聚合码场景 订单对象存在会员信息， 不可全部以上游为准。
        if (StringUtils.isNotEmpty(channelRetMsg.getChannelUserId())) {
            payOrder.setChannelUser(channelRetMsg.getChannelUserId());
        }


        boolean isSuccess = payOrderService.updateInit2Ing(payOrder.getId(), payOrder);
        if (!isSuccess) {
            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
                    "更新订单异常!");
        }

        isSuccess = payOrderService.updateIng2SuccessOrFail(payOrder.getId(), payOrder.getState(),
                channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelUserId(),
                channelRetMsg.getChannelErrCode(), channelRetMsg.getChannelErrMsg());
        if (!isSuccess) {
            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode()
                    , "更新订单异常!");
        }
    }


    /**
     * 统一封装订单数据
     **/
    private CommonPayResult packageApiResByPayOrder(UnifiedOrderReqVO bizRQ, UnifiedOrderRespVO bizRS,
                                                    PayOrderDO payOrder) {

        // 返回接口数据
        bizRS.setPayOrderId(payOrder.getId());
        bizRS.setOrderState(payOrder.getState());
        bizRS.setMchOrderId(payOrder.getMchOrderId());

        if (payOrder.getState() == PayOrderConstant.STATE_FAIL) {
            bizRS.setErrCode(bizRS.getChannelRetMsg() != null
                    ? bizRS.getChannelRetMsg().getChannelErrCode() : null);
            bizRS.setErrMsg(bizRS.getChannelRetMsg() != null
                    ? bizRS.getChannelRetMsg().getChannelErrMsg() : null);
        }

        return CommonPayResult.okWithSign(bizRS,
                configContextQueryService.queryMchApp(bizRQ.getMchId(),
                        bizRQ.getAppId()).getAppSecret());
    }
}
